<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/4/1
 * Time: 10:13
 */

namespace EasyTiktokSdk\Auth;

use EasyTiktokSdk\Application;
use EasyTiktokSdk\Kernel\Contracts\AccessTokenInterface;
use EasyTiktokSdk\Kernel\Exceptions\InvalidArgumentException;
use EasyTiktokSdk\Kernel\Exceptions\RuntimeException;
use EasyTiktokSdk\Kernel\Traits\HasHttpRequests;

use EasyTiktokSdk\Kernel\Traits\InteractsWithCache;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;


class AccessToken implements AccessTokenInterface
{

    use HasHttpRequests;
    use InteractsWithCache;

    /**
     * @var Application $app
     */
    protected $app;


    protected $baseUri = 'https://auth.tiktok-shops.com';

    /**
     * @var string
     */
    protected $requestMethod = 'GET';

    /**
     * @var string
     */
    protected $endpointToGetToken = '/api/v2/token/refresh';

    /**
     * @var string
     */
    protected $queryName;

    /**
     * @var array
     */
    protected $token;

    /**
     * @var int
     */
    protected $safeSeconds = 500;

    /**
     * @var string
     */
    protected $tokenKey = 'access_token';

    /**
     * @var string
     */
    protected $cachePrefix = 'easytiktoksdk.tiktok.access_token.';

    /**
     * AccessToken constructor.
     * @param Application $app
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    /**
     * @return array
     *
     * @throws \Exception
     */
    public function getRefreshedToken()
    {
        return $this->getToken(true);
    }

    /**
     * @param bool $refresh
     *
     * @return array
     *
     * @throws \Exception
     */
    public function getToken($refresh = false)
    {
        $cacheKey = $this->getCacheKey();
        $cache = $this->getCache();

        if (!$cache->has($cacheKey)) {
            throw new RuntimeException('token not found');
        }

        if (!$refresh && $cache->has($cacheKey) && $result = $cache->get($cacheKey)) {
            return $result;
        }

        $token = $this->requestToken();

        $this->setToken($token);

        return $token;
    }

    /**
     * @param array $token
     *
     * @return AccessTokenInterface
     *
     * @throws RuntimeException
     * @throws \EasyTiktokSdk\Kernel\Exceptions\InvalidArgumentException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function setToken(array $token)
    {
        $refresh_token_expire_in = $token['refresh_token_expire_in'];

        $this->getCache()->set($this->getCacheKey(), $token, ($refresh_token_expire_in - time()) + 50);

        if (!$this->getCache()->has($this->getCacheKey())) {
            throw new RuntimeException('Failed to cache access token.');
        }

        return $this;
    }

    public function refresh()
    {
        $this->getToken(true);

        return $this;
    }

    /**
     * @param array $credentials
     *
     * @return array
     *
     * @throws
     */
    public function requestToken()
    {
        $tokenData = $this->getToken();
        if ($tokenData['refresh_token_expire_in'] < time()) {
            throw new RuntimeException('refresh token expired');
        }
        $refreshToken = $tokenData['refresh_token'];

        $options = [
            'json' => [
                'app_key' => $this->app['config']['app_key'],
                'app_secret' => $this->app['config']['app_secret'],
                'refresh_token' => $refreshToken,
                'grant_type' => 'refresh_token'
            ]
        ];

        $response = $this->request($this->getEndpoint(), $this->requestMethod, $options);
        $result = json_decode($response->getBody()->getContents(), true);

        if (0 != $result['code'] && empty($result['data'][$this->tokenKey])) {
            throw new \Exception('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response);
        }

        return $result['data'];
    }

    /**
     * @param array $prams
     *
     * @throws \Exception
     */
    public function applyToRequest(&$prams)
    {
        $prams = array_merge($prams ? $prams : [], $this->getQuery());
    }

    /**
     * @return string
     */
    protected function getCacheKey()
    {
        return $this->cachePrefix.md5(json_encode($this->getCredentials()));
    }

    /**
     * The request query will be used to add to the request.
     *
     * @return array
     *
     * @throws \Exception
     */
    protected function getQuery()
    {
        return [$this->getQueryName() => $this->getToken()[$this->tokenKey]];
    }

    /**
     * @return string
     *
     * @throws \Exception
     */
    public function getEndpoint()
    {
        if (empty($this->endpointToGetToken)) {
            throw new \Exception('No endpoint for access token request.');
        }

        return $this->endpointToGetToken;
    }

    /**
     * @return string
     */
    public function getQueryName()
    {
        return $this->queryName ? $this->queryName : $this->tokenKey;
    }

    /**
     * @return string
     */
    public function getTokenKey()
    {
        return $this->tokenKey;
    }

    protected function getCredentials()
    {
        $openId = $this->app['config']['open_id'];
        if (empty($openId)) {
            throw new InvalidArgumentException('open id not found!');
        }
        return [
            'app_key' => $this->app['config']['app_key'],
            'app_secret' => $this->app['config']['app_secret'],
            'open_id' => $openId,
        ];
    }
}